using System;
using System.Drawing;
using System.IO;


namespace Ultima {
  public class Multis {
    private static readonly MultiComponentList[] m_Components = new MultiComponentList[0x4000];

    private static readonly FileIndex m_FileIndex = new FileIndex( "Multi.idx", "Multi.mul", 0x4000, 14 );
    public static MultiComponentList[] Cache {
      get { return m_Components; }
    }
    public static FileIndex FileIndex {
      get { return m_FileIndex; }
    }

    public static MultiComponentList GetComponents( int index ) {
      MultiComponentList mcl;

      index &= 0x3FFF;

      if ( index >= 0 && index < m_Components.Length ) {
        mcl = m_Components[index];

        if ( mcl == null )
          m_Components[index] = mcl = Load( index );
      } else
        mcl = MultiComponentList.Empty;

      return mcl;
    }

    public static MultiComponentList Load( int index ) {
      try {
        int length, extra;
        bool patched;
        Stream stream = m_FileIndex.Seek( index, out length, out extra, out patched );

        if ( stream == null )
          return MultiComponentList.Empty;

        return new MultiComponentList( new BinaryReader( stream ), length / 12 );
      } catch {
        return MultiComponentList.Empty;
      }
    }
  }


  public sealed class MultiComponentList {
    public static readonly MultiComponentList Empty = new MultiComponentList();
    private readonly Point m_Center;
    private readonly int m_Height;
    private readonly Point m_Max;
    private readonly Point m_Min;
    private readonly Tile[][][] m_Tiles;
    private readonly int m_Width;

    public MultiComponentList( BinaryReader reader, int count ) {
      this.m_Min = this.m_Max = Point.Empty;

      var allTiles = new MultiTileEntry[count];

      for ( int i = 0; i < count; ++i ) {
        allTiles[i].m_ItemID = reader.ReadInt16();
        allTiles[i].m_OffsetX = reader.ReadInt16();
        allTiles[i].m_OffsetY = reader.ReadInt16();
        allTiles[i].m_OffsetZ = reader.ReadInt16();
        allTiles[i].m_Flags = reader.ReadInt32();

        MultiTileEntry e = allTiles[i];

        if ( e.m_OffsetX < this.m_Min.X )
          this.m_Min.X = e.m_OffsetX;

        if ( e.m_OffsetY < this.m_Min.Y )
          this.m_Min.Y = e.m_OffsetY;

        if ( e.m_OffsetX > this.m_Max.X )
          this.m_Max.X = e.m_OffsetX;

        if ( e.m_OffsetY > this.m_Max.Y )
          this.m_Max.Y = e.m_OffsetY;
      }

      this.m_Center = new Point( -this.m_Min.X, -this.m_Min.Y );
      this.m_Width = ( this.m_Max.X - this.m_Min.X ) + 1;
      this.m_Height = ( this.m_Max.Y - this.m_Min.Y ) + 1;

      var tiles = new TileList[this.m_Width][];
      this.m_Tiles = new Tile[this.m_Width][][];

      for ( int x = 0; x < this.m_Width; ++x ) {
        tiles[x] = new TileList[this.m_Height];
        this.m_Tiles[x] = new Tile[this.m_Height][];

        for ( int y = 0; y < this.m_Height; ++y )
          tiles[x][y] = new TileList();
      }

      for ( int i = 0; i < allTiles.Length; ++i ) {
        int xOffset = allTiles[i].m_OffsetX + this.m_Center.X;
        int yOffset = allTiles[i].m_OffsetY + this.m_Center.Y;

        tiles[xOffset][yOffset].Add( (short)( ( allTiles[i].m_ItemID & 0x3FFF ) + 0x4000 ), (sbyte)allTiles[i].m_OffsetZ );
      }

      for ( int x = 0; x < this.m_Width; ++x ) {
        for ( int y = 0; y < this.m_Height; ++y ) {
          this.m_Tiles[x][y] = tiles[x][y].ToArray();

          if ( this.m_Tiles[x][y].Length > 1 )
            Array.Sort( this.m_Tiles[x][y] );
        }
      }
    }

    private MultiComponentList() {
      this.m_Tiles = new Tile[0][][];
    }

    public Point Min {
      get { return this.m_Min; }
    }
    public Point Max {
      get { return this.m_Max; }
    }
    public Point Center {
      get { return this.m_Center; }
    }
    public int Width {
      get { return this.m_Width; }
    }
    public int Height {
      get { return this.m_Height; }
    }
    public Tile[][][] Tiles {
      get { return this.m_Tiles; }
    }

    public Bitmap GetImage() {
      if ( this.m_Width == 0 || this.m_Height == 0 )
        return null;

      int xMin = 1000, yMin = 1000;
      int xMax = -1000, yMax = -1000;

      for ( int x = 0; x < this.m_Width; ++x ) {
        for ( int y = 0; y < this.m_Height; ++y ) {
          Tile[] tiles = this.m_Tiles[x][y];

          for ( int i = 0; i < tiles.Length; ++i ) {
            Bitmap bmp = Art.GetStatic( tiles[i].ID - 0x4000 );

            if ( bmp == null )
              continue;

            int px = ( x - y ) * 22;
            int py = ( x + y ) * 22;

            px -= ( bmp.Width / 2 );
            py -= tiles[i].Z * 4;
            py -= bmp.Height;

            if ( px < xMin )
              xMin = px;

            if ( py < yMin )
              yMin = py;

            px += bmp.Width;
            py += bmp.Height;

            if ( px > xMax )
              xMax = px;

            if ( py > yMax )
              yMax = py;
          }
        }
      }

      var canvas = new Bitmap( xMax - xMin, yMax - yMin );
      Graphics gfx = Graphics.FromImage( canvas );

      for ( int x = 0; x < this.m_Width; ++x ) {
        for ( int y = 0; y < this.m_Height; ++y ) {
          Tile[] tiles = this.m_Tiles[x][y];

          for ( int i = 0; i < tiles.Length; ++i ) {
            Bitmap bmp = Art.GetStatic( tiles[i].ID - 0x4000 );

            if ( bmp == null )
              continue;

            int px = ( x - y ) * 22;
            int py = ( x + y ) * 22;

            px -= ( bmp.Width / 2 );
            py -= tiles[i].Z * 4;
            py -= bmp.Height;
            px -= xMin;
            py -= yMin;

            gfx.DrawImageUnscaled( bmp, px, py, bmp.Width, bmp.Height );
          }

          int tx = ( x - y ) * 22;
          int ty = ( x + y ) * 22;
          tx -= xMin;
          ty -= yMin;
        }
      }

      gfx.Dispose();

      return canvas;
    }

    #region Nested type: MultiTileEntry
    private struct MultiTileEntry {
      public int m_Flags;
      public short m_ItemID;
      public short m_OffsetX, m_OffsetY, m_OffsetZ;
    }
    #endregion
  }
}